from typing import Optional, Dict, Any
import httpx
from app.core.logger import logger
import time
import asyncio

class HttpClient:
    """
    封装 httpx 异步客户端：
    - 连接池复用
    - 自动重试
    - 请求耗时日志
    - 错误捕获
    """
    _client: Optional[httpx.AsyncClient] = None

    @classmethod
    def get_client(cls) -> httpx.AsyncClient:
        if cls._client is None:
            cls._client = httpx.AsyncClient(
                timeout=httpx.Timeout(10.0, connect=5.0),
                limits=httpx.Limits(max_connections=100, max_keepalive_connections=20),
                follow_redirects=True
            )
        return cls._client
    
    @classmethod
    async def request(
        cls,
        method: str,
        url: str,
        params: Optional[Dict[str, Any]] = None,
        data: Optional[Dict[str, Any]] = None,
        json: Optional[Dict[str, Any]] = None,
        headers: Optional[Dict[str, str]] = None,
        retries: int = 2,
    ) -> Dict[str, Any]:
        """
        发起异步请求（带重试与日志）
        """
        client = cls.get_client()

        for attempt in range(retries + 1):
            start_time = time.perf_counter()
            try:
                response = await client.request(
                    method=method.upper(),
                    url=url,
                    params=params,
                    data=data,
                    json=json,
                    headers=headers
                )
                duration = (time.perf_counter() - start_time) * 1000
                logger.info(f"[HTTP] {method.upper()} {url} - {response.status_code} ({duration:.2f} ms)")

                response.raise_for_status()
                return response.json()

            except httpx.RequestError as e:
                logger.warning(f"[HTTP ERROR] 请求失败 {url}：{e}")
                if attempt < retries:
                    await asyncio.sleep(0.5 * (attempt + 1))
                    continue
                raise e
            except httpx.HTTPStatusError as e:
                logger.error(f"[HTTP STATUS ERROR] {e.response.status_code} - {e.response.text}")
                raise e

    @classmethod
    async def get(cls, url: str, params: Optional[Dict[str, Any]] = None, **kwargs):
        return await cls.request("GET", url, params=params, **kwargs)

    @classmethod
    async def post(cls, url: str, json: Optional[Dict[str, Any]] = None, **kwargs):
        return await cls.request("POST", url, json=json, **kwargs)

    @classmethod
    async def close(cls):
        if cls._client:
            await cls._client.aclose()
            cls._client = None